本章节内容基于《Programming in Scala》 2010
基础定义
变量定义以 var / val 开始
Scala 有两种变量: var
和 val
:
var
: 类似final
变量,初始化后不可再被赋值val
: 普通变量
val msg : String = " Hello, world! "
这里也可以通过类型推断来避免说明数据类型
函数定义以 def 开始
def max(x:Int, y: Int) : Int = {
if ( x > y ) x
else y
}
所有方法的参数都是val
如果没有显示的返回return
语句,方法返回最后一次计算得到的值。以鼓励简化方法,采用小方法,而不是复杂的大方法。
符号可以做方法名
可以使用更为灵活的调用符号方法
0 to 10 ;
0.to(10); //it is the same
即0这个字面量对象调用了Int类的to方法
注释方法
//
是行注释;/* */
是段落注释
集合类型 (Collection)
数组 Array
脚本的命令行参数,存储在 args
的数组中。数组的定位用()
,而非[]
。即只有args(0)
,而没有args[0]
列表 List
数组(array)是可变的同类对象序列,Array[String] 实例化后长度固定,但元素值都可变。 列表(list)是不可变的同类对象序列,一旦创建不可改变。
集合 Set
set 较 list, 不能包含重复的元素。 Set 是一个trait,而不是class,进而又分化成了可变(mutable)和不可变(immutable)两类特质。
有两种HashSet类,各扩展了可变和不可变两种特质。两类不同的HashSet的+
方法,其表现就不一样。
可变的把新增元素加入自身,不可变的返回一个新集合
映射 map
下列代码:
1 -> "example" // also can be (1).->("example")
返回[Int, String]
的Tuple。在Scala中,任意对象都能用户->
方法,返回包含键值对的二元组,这类机制为隐式转换
理解函数式编程
区别指令式编程(传统的)和函数式编程的一个关键点在于代码中,是否包含 var。 函数式编程风格尝试着不用任何var来编程。
指令式:
def printArgs(args: Array[String]) : Unit = {
var i = 0
while (i < args.length){
println(args(i))
i += 1
}
}
函数式:
def printArgs(args: Array[String]) : Unit = {
for (arg <- args){
println(arg)
}
}
上述printArgs有副作用,即打印到标准输出,因此不是纯函数式的。 若一个函数返回Unit,意味着函数不返回任何有用的值,因此它的作用只通过副作用来体现。
def formatArgs(args: Array[String]) = args.mkString("\n")
上述formatArgs函数,就没有任何副作用了。
对象类和对象
存在 public 和 private 两类修饰符,默认为public
可以构建抽象类,即在class前加上 abstract 修饰符即可。抽象类中的方法定义,只要没有定义,即为抽象方法,不需有抽象修饰符。 对应地,抽象类不可以被实例化。
Java 包括四个命名空间:字段、方法、类型、包。这意味着,一个字段和一个方法可以拥有相同的名称。 Scala只有两个命名空间:值(包括字段、方法、包、单例对象)和类型(类和特质)。因此,如果把一个方法和一个字段设成同名,则会造成编译错误。
程序执行方式
构建自己的main方法
当一个singleton
单例对象中包含main
方法(带合适签名?),就可以用作是程序的入口点
通常情况下,单个Scala源代码可视为一个脚本,利用scala
命令,可以直接执行其中的表达式。若需要构建完整的程序并执行,需要利用scalac
命令对源代码进行编译。或者利用fsc
建立一个后台的scala编译服务器(fast Scala compiler)
编译完成后,利用scala
命令执行包含main
方法的独立对象名,以及其参数。
扩展Application特质
将scala.Application
特质混入单例对象,无需定义main方法,而是该对象定义中的所有语句,即包含在它的primary constructor
主构造中的所有代码都会被执行。
特殊的数据类型和操作符
跨行的字符串
引入”“”
来作为大段包含转义字符或若干行的字符串的开始和结束。
如果需要避免隔行间的空格,需要在每行开始处放置一个|
管道符。
println("""|Welcome to Ultamix 3000
|Type "Help" for help""")
也说要用stripMargin
方法,但貌似不用也可以。
符号字面量
没见过类似的概念,用于表示一个特定的符号,而非字符串。
scala> val s = 'aSymbol
s: Symbol = 'aSymbol
scala> s.name
res3: String = aSymbol
函数字面量
即匿名的函数,可以包含在一个函数内,与引用函数共享参数。在运行过程中,函数字面量实例化为函数值(function value
)。下面是几种在过滤集合时,应用函数字面量的例子。注意在使用_
占位符时,每个参数只能出现一次,而不能反复出现。
val someNumbers = List(-11, -10, 0, 5, 10)
scala > someNumbers.filter( (x : Int) => x > 0)
scala > someNumbers.filter( x => x > 0)
scala > someNumbers.filter( _ => _ > 0)
控制结构
if
这样的控制结构是表达式,可以产生值。而while
这样的循环就不是表达式,因为其结果类型是Unit
,其所有效果都是依靠其副作用来完成的。
for
是一个表达式,因为它可以利用yield
关键字,生成一个结果循环体集合对象。
高阶函数
柯里化
以下述代码为例:
scala > def curriedSum(x: Int)(y: Int) = x + y
curriedSum: (x: Int)(y: Int)Int
scala > curriedSum(1)(2)
res1: Int = 3
scala > scala> def first(x: Int) = (y: Int) => x + y
first: (x: Int)Int => Int
scala> val second = first(1)
second: Int => Int = <function1>
scala> second(2)
res3: Int = 3
柯里化函数curriedSum
实际就是后续两个函数first
和 second
的结合体。
在调用过程中,首先准备一个first
函数,它接受参数x
,然后返回第二个函数。
第二个函数接受(y:Int)
为参数,输出x+y
的来作为结果。
此例中,在second
定义时通过调用first
函数,并赋予其参数x
为1,从而返回第二个接受y
为参数的函数。当调用second
时,给参数y
赋值为2,进而得到结果值3。
借贷模式
以下述代码为例:
def withPrintWriter(file: File, op: PrinterWriter => Unit){
val writer = new PrintWriter(file)
try{
op(writer)
} finally {
writer.close()
}
}
withPrintWriter(
new File("date.txt"),
writer => writer.println(new java.util.Date)
)
此处,对于资源writer
的管理,都是交给withPrintWriter
函数本身来完成的,而需要写入内容的业务操作,则作为一个函数值,传递给withPrintWriter
,借取writer来执行。执行结束后,由withPrintWriter
函数再来关闭资源。
借贷模式可以配合柯里化一起使用。由于Scala允许在只有一个参数的时候,用{}
,而不是()
来引用参数。因此,可能将witherPrintWriter
的两个参数利用柯里化而转换成两个独立的参数,这样第二个参数就可以使用{}
括起,而使得函数参数看起来像是过程语句了。具体见书 P110